Mathias Bynens

Optimizing the asynchronous Google Analytics snippet

Published · tagged with JavaScript, performance

Google has been beta-testing their asynchronous Analytics snippet for a while now. The code has been revised several times already, but still I think it can use some more optimizations.

The latest snippet is the following:

<script>
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXX-X']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
</script>

How it works

In case you’re wondering what’s with the _gaq variable, this is described in the Google Analytics Asynchronous Tracking Guide™:

The _gaq object is what makes the asynchronous syntax possible. It acts as a queue, which is a first-in, first-out data structure that collects API calls until ga.js is ready to execute them. To add something to the queue, use the _gaq.push method.

To be more precise — initially, _gaq is just a plain JavaScript Array. The snippet then uses the native push method to add some entries to the end of the array, such as specifying your account ID. After ga.js is loaded from Google’s servers, the _gaq.push method is replaced with a custom method, which executes all entries in the _gaq array. At this point, _gaq is no longer an Array, but an [Analytics] Object, and instant execution of the tracking methods is possible.

The next part of the snippet — (function() { … })(); — injects a new script element in the document to load ga.js, where all the magic happens. This bit of code is wrapped inside a self-invoking anonymous function immediately-invoked function expression, to avoid polluting the global scope with the temporary variables created in the process.

Possible optimizations

Don’t use _gaq || []

The first line of the script reads:

var _gaq = _gaq || [];

This line is there to allow multiple Analytics snippets in the same document. It ensures that the second snippet doesn’t overwrite a _gaq defined by the first. In other words, if _gaq is already defined, the script will continue to use that variable; if not, it creates a new array instead. However, there are far better techniques to instantiate multiple trackers than just including the same snippet twice, with a different account ID.

So, unless you’re using multiple Analytics snippets in the same document using bad code, you can safely use this instead:

var _gaq = [];

One push should suffice

The example snippet adds two elements to the _gaq array, by using the push method twice.

_gaq.push(['_setAccount', 'UA-XXXXX-X']);
_gaq.push(['_trackPageview']);

While the above is probably a bit more readable, this can perfectly be written as a single push statement:

_gaq.push(['_setAccount', 'UA-XXXXX-X'], ['_trackPageview']);

Of course, if you’re using more advanced tracking settings, you’ll need to push even more commands. You can safely do this for all of them at once.

Combined with the previous optimization, we don’t even need .push anymore:

var _gaq = [['_setAccount', 'UA-XXXXX-X'], ['_trackPageview']];

(Note that since _gaq needs to be a global variable anyway, we could even omit var here, saving another 4 bytes. I decided not to include this optimization because it’s confusing, it looks sloppy, and it would throw a reference error in ES5 strict mode.)

Don’t set ga.type

Before inserting the dynamically created script element into the DOM, its type attribute is set to text/javascript:

var ga = document.createElement('script');
ga.type = 'text/javascript';

This is entirely unnecessary. In every single A-grade browser, script elements default to text/javascript when the type attribute is omitted. Moreover, in HTML5, the type attribute on script elements is optional. It’s safe to omit this line.

Declare all variables in one go

The snippet uses two variables inside the anonymous function: ga, which holds the dynamically generated script element; and s, which is a reference to the first script element to occur in the document.

var ga = document.createElement('script');
// …
var s = document.getElementsByTagName('script')[0];

By declaring both vars in one go and by renaming ga into g, we can get rid of some bytes:

var g = document.createElement('script'),
s = document.getElementsByTagName('script')[0];

Note: If support for Firefox < 9 and BlackBerry OS 5 is not an issue, you could just use document.scripts[0].

Create shortcut references for recurring vars

As you can see, document is used twice. By passing document as an argument to the anonymous function, we can squeeze out even more bytes:

(function(d) {
var g = d.createElement('script'),
s = d.getElementsByTagName('script')[0];
// …
})(document);

This also improves run-time performance, since it avoids scope lookups for document.

We can go even further by also passing "script" as an argument, since that string is used twice as well:

(function(d, t) {
var g = d.createElement(t),
s = d.getElementsByTagName(t)[0];
// …
})(document, 'script');

That’s just to save bytes though.

To comply with the “require parens around immediate invocations” option in JSLint, you’ll have to use (function() { }()); instead of (function() { })();. Personally, I agree; (fn()) makes more sense than (fn)().

(function(d, t) {
var g = d.createElement(t),
s = d.getElementsByTagName(t)[0];
// …
}(document, 'script'));

Alternatively, you could just declare t as a variable within the IIFE:

(function(d) {
var t = 'script',
g = d.createElement(t),
s = d.getElementsByTagName(t)[0];
// …
}(document));

Use the protocol check only when it’s needed

The ga.js file needs to be served via the same protocol as the document it’s used in. To make sure of this, the default Analytics snippet includes a simple protocol check:

ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';

Instead of document.location, you could use window.location to save two bytes, or just location to save a total of 9 bytes.

Your site should run over HTTPS/TLS, redirecting requests from HTTP to the HTTPS version. Even if you don’t support HTTPS, the TLS check isn’t needed at all, and you can safely remove it — just always serve the Google Analytics script over HTTPS.

Aside from saving a few bytes, I found that omitting the protocol check and replacing it with a hardcoded string is up to two thousand times faster. There’s no reason not to change this.

ga.src = 'https://ssl.google-analytics.com/ga.js';

Heck, if you’re doing the right thing and serving your site over HTTPS, we can even remove the https: scheme so we end up with a protocol-relative URL:

ga.src = '//ssl.google-analytics.com/ga.js';

However, now that TLS is encouraged for everything and doesn’t have performance concerns, this technique is now an anti-pattern. If the asset you need is available over HTTPS, then always use the https:// asset.

Asynchronous loading

To ensure the dynamically inserted <script> element doesn’t block rendering in any browsers, it gets the async attribute. This causes ga.js to be executed asynchronously, as soon as it’s loaded.

ga.async = true;

Since async is a boolean attribute in HTML, its presence on an element is enough. As a result, we can use JavaScript to set the async property of the ga object to any truthy value.

ga.async = 1;

Another 3 bytes saved.

Taking it one step further, we could merge this together with the next line of code, where the src property is set:

ga.async = 1;
ga.src = 'https://ssl.google-analytics.com/ga.js';

Since the URL that’s set as the src is a truthy string, we could just as well have written the following:

ga.async = ga.src = 'https://ssl.google-analytics.com/ga.js';

While this is slightly less readable, it saves another two bytes. (Hat tip: Jake Archibald.)

Update: Kyle Simpson points out that all dynamically inserted <script>s default to .async=true as per the spec. Most browsers have always implemented it this way. The only exceptions are Firefox 3.6 and Opera 10–12, who execute scripts in insertion order by default, which may cause an unnecessary delay. By setting .async=true explicitly we make sure ga.js doesn’t wait for other previously loaded scripts and doesn’t block any subsequently loaded scripts. This line of code only affects Firefox 3.6. (Sadly, it doesn’t seem to make a difference in Opera). If you’re not using any other scripts, or Firefox 3.6 support is not an issue, you can safely remove it to save even more bytes. For more information, see “HTML5 script execution changes in Firefox 4”.

Update (August 2011): Now that Firefox 6 is released, it feels like the right time to just drop the .async.

Update (January 2012): Steve Souders did some additional research and found that setting .async = true also has a positive effect in OmniWeb 622 (just like in Firefox 3.6). While it’s definitely good to know, this doesn’t change anything for me. I commented on Steve’s post, explaining that setting .async = true for dynamically inserted scripts is only worthwhile if three conditions are met.

Final result

<script>
var _gaq = [['_setAccount', 'UA-XXXXX-X'], ['_trackPageview']];
(function(d, t) {
var g = d.createElement(t),
s = d.getElementsByTagName(t)[0];
g.src = 'https://ssl.google-analytics.com/ga.js';
s.parentNode.insertBefore(g, s);
}(document, 'script'));
</script>

We have reduced the original snippet of 440 bytes to one of 276 bytes with better performance. Finally, we can minify this, by removing unnecessary whitespace and semicolons:

<script>var _gaq=[['_setAccount','UA-XXXXX-X'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//ssl.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script'))</script>

The optimized code minifies to 239 bytes and executes faster than the original (440 bytes).

As mentioned before, if you don’t care about Firefox < 9 or BlackBerry OS 5 support, it gets even better — because then the use of document.getElementsByTagName('script') can be avoided by using the document.scripts DOM tree accessor:

<script>
var _gaq = [['_setAccount', 'UA-XXXXX-X'], ['_trackPageview']];
(function(d) {
var g = d.createElement('script'),
s = d.scripts[0];
g.src = 'https://ssl.google-analytics.com/ga.js';
s.parentNode.insertBefore(g, s);
}(document));
</script>

That’s just 267 characters, which minify to 219 bytes.

<script>var _gaq=[['_setAccount','UA-XXXXX-X'],['_trackPageview']];(function(d){var g=d.createElement('script'),s=d.scripts[0];g.src='//ssl.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document))</script>

Of course, it’s possible to reduce the snippet’s byte size even more, for example by using placeholder arguments instead of var, or by getting rid of the immediately-invoked function expression. It’s important to watch out for global namespace pollution though. I’d say the above snippet is the perfect balance between efficiency and readability, and would make a fine replacement for the default snippet provided by Google.

Of course, this piece of code could easily be made into a generic “asynchronous script loader” snippet that works with any script URL (not just ga.js).

How to use it?

Google recommends placing the asynchronous snippet as high as possible in the document:

One of the main advantages of the asynchronous snippet is that you can position it at the top of the HTML document. This increases the likelihood that the tracking beacon will be sent before the user leaves the page. We’ve determined that on most pages, the optimal location for the asynchronous snippet is at the bottom of the <head> section, just before the closing </head> tag.

However, you can place the snippet in the <body> as well, be it at the top, or at the bottom. If you’re already using separate .js files for other scripts, you can even include the snippet in there so it will be cached along with the other scripts. This can be a valid reason not to put the code in the <head> after all.

Update (March 2013): Universal Analytics

Google Universal Analytics is now in public beta and features a new snippet:

<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-XXXX-Y');
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->

After deobfuscation, this looks like:

<!-- Google Analytics -->
<script>
(function(window, document, strScript, url, variableName, scriptElement, firstScript) {
window['GoogleAnalyticsObject'] = variableName;
window[variableName] = window[variableName] || function() {
(window[variableName].q = window[variableName].q || []).push(arguments);
};
window[variableName].l = 1 * new Date();
scriptElement = document.createElement(strScript),
firstScript = document.getElementsByTagName(strScript)[0];
scriptElement.async = 1;
scriptElement.src = url;
firstScript.parentNode.insertBefore(scriptElement, firstScript)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

ga('create', 'UA-XXXX-Y');
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->

By applying the optimizations discussed in this post, this becomes:

<script>
(function(window, document, variableName, scriptElement, firstScript) {
window['GoogleAnalyticsObject'] = variableName;
window[variableName] || (window[variableName] = function() {
(window[variableName].q = window[variableName].q || []).push(arguments);
});
window[variableName].l = +new Date;
scriptElement = document.createElement('script'),
firstScript = document.scripts[0];
scriptElement.src = 'https://www.google-analytics.com/analytics.js';
firstScript.parentNode.insertBefore(scriptElement, firstScript)
}(window, document, 'ga'));

ga('create', 'UA-XXXX-Y');
ga('send', 'pageview');
</script>

Minified, this becomes:

<script>(function(G,o,O,g,l){G.GoogleAnalyticsObject=O;G[O]||(G[O]=function(){(G[O].q=G[O].q||[]).push(arguments)});G[O].l=+new Date;g=o.createElement('script'),l=o.scripts[0];g.src='https://www.google-analytics.com/analytics.js';l.parentNode.insertBefore(g,l)}(this,document,'ga'));ga('create','UA-XXXX-Y');ga('send','pageview')</script>

Google’s original, semi-compressed version is 440 bytes. The optimized, minified version is 332 bytes and executes slightly faster. Don’t use the optimized snippet if you care about Firefox < 9 or BlackBerry OS 5 or OmniWeb 622.

About me

Hi there! I’m Mathias. I work on Chrome DevTools and the V8 JavaScript engine at Google. HTML, CSS, JavaScript, Unicode, performance, and security get me excited. Follow me on Twitter, Mastodon, and GitHub.

Comments

Brian wrote on :

Google has different HTTP and HTTPS subdomains (‘www’ and ‘ssl’ respectively) for performance reasons. Using ‘www.’ for HTTPS requests is going to be slower and it may not even work in some geographic areas.

wrote on :

Brian: Interesting. Can you provide any links to documents backing that up? It would seem the name of a subdomain shouldn’t matter on a properly configured web server, SSL or not.

Nate Cavanaugh wrote on :

Most ‘micro-optimizations’ like this end up consuming more time than the benefit they provide. When you gzip the page, what’s the delta of the changes before and after? While not everyone gzips their page, if they’re worried about 200 bytes, they are either already gzipping, or should be.

Usually for space saving stuff like this, I try to use the machine as much as possible for optimizations, and keep the original code as readable as possible.

Overall though, I think this is a cool look at how the new async Google Analytics code works (especially the object replacement which is quite clever).

Do you have any total before and after on the speed tests?

Thanks for the writeup :)

wrote on :

Nate: I created three HTML5 documents: one with the original asynchronous Analytics snippet, one with the optimized but non-minified snippet, and another with the optimized and minified snippet. All files are served with gzip compression enabled.

Here are the file sizes of the different HTML5 documents, before and after compression:

  • With the original snippet: 735 bytes, after gzip 432 bytes
  • With the optimized (but non-minified) snippet: 614 bytes, after gzip 389 bytes
  • With the optimized and minified snippet: 569 bytes, after gzip 380 bytes

As you can see, there’s an improvement of ± 23% in byte size when using the optimized snippet without gzip. When using gzip, the compression rate is still around ± 12%.

Note that gzip works by storing common blocks of data and compressing those, so the results may vary depending on the rest of your HTML document. This is also the reason why I created HTML files instead of .js files and testing those sizes before and after compression.

I agree this is a matter of micro-optimization, but it’s important to look further than just byte size. An equally important improvement of the optimized snippet is the increased performance.

Kyle Simpson wrote on :

One thing to note about this new version of GA async is that they’ve lost the robustness against the <base> tag problem in IE6. The problem basically is that you have to insert a script tag before (in the DOM) the <base> tag appears, or you will crash IE6. It has to do with IE6 not recognizing the close of the <base> tag and behaving incorrectly as a result.

The solution (albeit less graceful) is to always insert scripts as the first child of the <head>, ensuring it comes before the <base> tag if one appears. The way you do that is get a reference to the head element (instead of a script), and insertBefore() on its firstChild. You have to do an additional check to make sure the object reference is valid, so the extra code is probably why Google abandoned it.

But I do think this snippet should come with the caveat that you cannot safely use it with document that has a <base> tag in it.

wrote on :

Kyle: Good point; I hadn’t thought of that. In my opinion, an even better solution is to explicitly ‘close’ the base tag inside a conditional comment for IE 6 only.

<base href="https://example.com/foo/bar/"><!--[if lte IE 6]></base><![endif]-->

You pretty much have to do this anyway — otherwise IE6 considers all following elements to be descendants of the <base> element instead of <head>. Obviously, this can cause severe performance/bandwidth issues.

j@ubourg wrote on :

Sadly, the async property set as true or 1 will not work (see jQuery.ajax() async versus HTML5 <script async>). You’d better go with something like this:

var _gaq = [['_setAccount', 'UA-XXXXX-X'], ['_trackPageview']];
(function(d, t, a) {
var g = d.createElement(t),
s = d.getElementsByTagName(t)[0];
g[a] = a;
g.src = '//www.google-analytics.com/ga.js';
s.parentNode.insertBefore(g, s);
}(document, 'script', 'async'));

wrote on :

j@ubourg: I don’t think I understand what you mean. In which browser does script.async = true fail? (Other than Opera, which doesn’t currently support the async and defer attributes at all.)

j@ubourg wrote on :

Mathias: Firefox, and I learned it the hard way (as you can see in the jQuery dev thread I linked to). Experiments clearly showed a script tag with async = 1 was acting synchronously whereas a script tag with async = "async" was acting asynchronously (tested with PHP scripts using delays to track the chain of script tag execution and how they were scheduled by browsers). Hope this helps.

wrote on :

j@ubourg: Interesting. Which browsers and browser versions did you test? Are your tests available online?

After running the Analytics snippet in the console and checking the generated HTML source using Firebug, it seems like everything is in order. The following code is inserted dynamically:

<script async="" src="//www.google-analytics.com/ga.js">

Which is exactly what we’re looking for — in HTML, async="" is the same as async="async" or just async, for that matter. See boolean attributes in HTML5.

Are you saying all browsers implement this incorrectly?

var foo = document.createElement('script');
foo.async = true; // or foo.async = 1;

j@ubourg wrote on :

Mathias: I would have to dig my tests up. I’d say it was a couple Firefox subversions ago. Don’t trust the generated code: make a PHP script with a delay param that’ll wait for X seconds, put a couple script tag injections in your page (with the PHP script with different delay parameter values as src) and see how it behaves. I guarantee you’ll be surprised ;)

My guess is Firefox only acts asynchronously when async = "async" but it may have been fixed since my last tests (I can’t find my old tests right now).

Browsers behaviors are quite inconsistent in the script loading department. Some will block on injected script tags, others will always act asynchronously with them. Firefox is the most consistent, provided you set the async property properly (again, to be tested with latest, they could have fixed the issue, I haven’t explored this in a while).

If you wanna see how much ‘fun’ I used to have with script tag injection, I suggest you check this thread. Not related to the issue at hand but gives a good view at how crazy some browser’s implementations can be and how crazy you can go with them.

Rob Flaherty wrote on :

Great post! After reading this I took a shot at optimizing the asynchronous tracking code from the analytics service Clicky. But Clicky’s async snippet is modeled after GA’s, so the code ended up being more or less a direct copy of yours. =)

Original code (raw):

<script>
var clicky = { log: function() { return; }, goal: function() { return; }};
var clicky_site_id = XXXXXX;
(function() {
var s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = (document.location.protocol == 'https:' ? 'https://static.getclicky.com' : 'http://static.getclicky.com') + '/js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(s);
})();
</script>

<a title="Web Statistics" href="http://getclicky.com/162739"></a>
<noscript><p><img alt="Clicky" width="1" height="1" src="http://in.getclicky.com/XXXXXXns.gif" /></p></noscript>

Optimized version (gist):

<script>
var clicky = { log: function() { return; }, goal: function() { return; }},
clicky_site_id = XXXXXX;
(function(d, t) {
var c = d.createElement(t),
s = d.getElementsByTagName(t)[0];
c.async = 1;
c.src = '//static.getclicky.com/js';
s.parentNode.insertBefore(c, s);
})(document, 'script');
</script>

<!-- This supports tracking for users with JavaScript disabled. This should go in <body> -->
<noscript><img alt="" width="1" height="1" src="http://in.getclicky.com/XXXXXXns.gif" /></noscript>

wrote on :

Nice work, Rob! You can take it even further if you want, by optimizing these lines:

var clicky = { log: function() { return; }, goal: function() { return; } },
clicky_site_id = XXXXXX;

You don’t really need the return, an empty function has the same effect. To create an empty function it’s enough to initialize a new Function object.

var clicky = { log: Function(), goal: Function() };

Also, since the function is used twice, you can store its return value in a variable to improve efficiency (and save some more bytes in the process).

var f = Function(),
clicky = { log: f, goal: f };

The full code can be viewed in my fork of your gist.

Rob Flaherty wrote on :

Very nice! I knew there had to be a way to improve those empty functions…

BTW, I’ve been testing out the snippet on my blog today and it indeed works.

Sander Aarts wrote on :

Nice article Mathias. You pass in document and 'script' as parameters to the self-invoking anonymous function. Passing document may have a very slight performance gain (haven’t tested it) as it creates a reference to the document in the function’s local scope, without the need for the script to run up the scope chain. But what’s the gain of passing 'script', instead of initializing it within the functionality body? That would make it easier to read in my opinion and it would even 1 byte smaller :) when combined with the other declarations.

wrote on :

Sander: The only reason I’m passing 'script' is to save some bytes. I haven’t tested the performance gain — I doubt there is any. How would declaring the variable containing 'script' inside the functionality body make it any smaller though? I made a comparison of both snippets, and they’re both exactly the same size when minified. Before minifying (and using proper code indenting) your version would be even larger (by a few bytes). Am I missing something here?

Antti Kokkonen wrote on :

Thanks a lot for this. Poetrically beautiful optimization! and more importantly: works perfectly :)

And like that is not enough, the flow of optimization was very well presented, and will help me optimize other snippets as well.

Martin wrote on :

Nice article! I’ll try it out.

Do you know same snipped to fix document.write on synchronously loaded scripts ?

I was trying this, but I have problems on Chrome:

document.write = function(t) {
var b = (document.getElementsByTagName('body')[0] || document.documentElement);
b.innerHTML = b.innerHTML + t;
};

Thanks in advance!

wrote on :

Martin: You really shouldn’t be using document.write hacks when there’s an alternative available.

Regarding your code sample, why use document.getElementsByTagName('body')[0] instead of just document.body? Please note that resetting the innerHTML of the entire body is evil and shouldn’t be done ever.

Martin wrote on :

Mathias: Thanks for your reply. I’m not using document.write, but I’m trying to resolve ad tags loading, like AdSense scripts. Those scripts are using document.write and it is impossible to load them async. For that reason I need to overwrite document.write.

Wim Leers wrote on :

Aside from saving a few bytes, I found that omitting the protocol check and replacing it with a hardcoded string is up to two thousand times faster.

Two thousand times faster is a lot. In Google Chrome 5, on my hardware, it’s “only” between 200 and 300 times faster. I don’t think this relative speed-up is very relevant: what’s 1 ms versus 0.05 ms? We don’t care about a single ms. You should’ve included the absolute speedup. For me, the hardcoded string only needs 0.8 ms, the ternary protocol check needs a whopping 180 ms. And this 180 ms is very relevant, since it becomes very noticeable.

I really wonder why Google doesn’t store the different ga.js files under the same path, i.e. make http://www.google-analytics.com/ga.js serve up the HTTP version and https://www.google-analytics.com/ga.js the SSL version.

You’re absolutely right in questioning this. It would allow for more elegant GA initialization code, as you already said — by using a protocol-relative URL and thereby obsoleting the document’s protocol check. However, this is probably a matter of optimizing the download speed of ga.js: it’s easier to manage and optimize HTTPS and HTTP servers separately (by running them on different domains). I can’t give you details because I’m not very knowledgeable in that area, but I do know that this is true, at least today, and for the typical server set-up. Although I agree that Google should focus on this as the next step of improving GA performance — they’ve got enough brain power to make it fast. 180 ms for the default ternary check in one of the fastest browsers currently in existence, on very modern hardware indicates that it’s likely many multiple times slower on older computers with slower browsers. And thus an excellent additional performance boost.

The shorter variable names and passing of variables via the anonymous function’s parameters to save bytes are completely overkill in my opinion. They reduce legibility to gain mere bytes.

Overall, very nice article. I’m definitely going to use this optimization on my websites! :)

wrote on :

Wim:

Two thousand times faster is a lot. In Google Chrome 5, on my hardware, it’s “only” between 200 and 300 times faster.

Yeah, same here in Chrome. The bigger difference occurred in Firefox (3.6.3 at the time), and since this was the most impressive result I had seen, that’s the one I referred to. Of course, YMMV :)

I don’t think this relative speed-up is very relevant: what’s 1 ms versus 0.05 ms? We don’t care about a single ms. You should’ve included the absolute speedup. For me, the hardcoded string only needs 0.8 ms, the ternary protocol check needs a whopping 180 ms. And this 180 ms is very relevant, since it becomes very noticeable.

You should note that the test case measures the speed for 100,000 iterations of that single line of code — not one iteration. Feel free to view the source for more information.

As you mentioned before, the results of the test case do not only depend on the browser, but also on the hardware and CPU usage. These client-side variables are impossible to control.

That’s why I figured I’d just go with a relative measure instead. If the Mac version of Chrome 5 executes a certain statement 300 times faster than another, it’s safe to assume this will be the case for any other Mac configuration with the same browser/version, even though the absolute numbers may differ based on processor/CPU usage etc.

But again, it was just an example. If you want to collect raw data on different computer/browser/OS combinations, feel free to use the test case — that’s what it’s there for!

The shorter variable names and passing of variables via the anonymous function’s parameters to save bytes are completely overkill in my opinion. They reduce legibility to gain mere bytes.

It seems legibility doesn’t really matter for a snippet that is blindly copy-pasted all over the Internet. Most people using it don’t seem to realize what exactly the script does — else, someone else would have written this article much earlier. The protocol check is the perfect example of this. Of course, I don’t have the exact numbers, but I bet that over 90% of all websites with that check in their code don’t need it. Let’s face it, most sites are HTTP-only and don’t even work over HTTPS/SSL. I can see why Google would include it in their default snippet though.

Having said that, the optimizations you’re talking about do not only save bytes, but also offer a small performance gain. Passing document as a variable to the function creates a reference to document in the function’s local scope. The advantage of this is that every time document is used from inside the function (three times in the original snippet), the script doesn’t need to run up the scope chain to look it up.

Wim Leers wrote on :

You should note that the test case measures the speed for 100,000 iterations of that single line of code — not one iteration. Feel free to view the source for more information.

Hah! I can’t believe I didn't notice that. You’re absolutely right to mention the relative difference then :)

It seems legibility doesn’t really matter for a snippet that is blindly copy-pasted all over the Internet.

I thought about this too, but I disagree softly. While it’s true what you say (about blindly copying and about the small performance gain), I think it increases the threshold to understanding unnecessarily.

Although I can definitely agree with your point-of-view too: it’s just a matter of taste — I prefer legibility over minor performance gains. But yes, it does make perfect sense to go for every performance gain possible when your code is deployed on millions of websites.

Clearly, even the only minor criticisms I could formulate are easily refutable. Which means this is very well written.

Thanks for taking the time to answer! :)

Kyle Simpson wrote on :

On the topic of why Google uses SSL on one domain and non-SSL on another domain… I think it may be more performant to do so because the web server needs to spin up the SSL Engine for a single virtual-host, meaning (I bet) that engine is spun up (or at least accessed I’m sure) for every request to that comes into a SSL enabled virutal-host. This would mean all non-SSL traffic would be slightly slower for the sake of that SSL engine start or access. Just a thought.

I agree it’s annoying not to be able to use the :// hack… but perhaps Google believes there’s a greater performance gain elsewhere that makes up for the few bytes and the ternary. Not sure.

wrote on :

Kyle: Brian just tweeted that using //www.google-analytics.com/ga.js causes an SSL security warning in IE6 on HTTPS pages. He continues:

I thought it was solely a geographic performance issue, but it turns out the browser issue is the main reason.

So now we know :)

FYI, there’s a test case and a screenshot of the dialog that appears in IE6.


Update: I wrote a Bash script that checks the SSL certificate for a given domain and returns its subject data, which contains the domain(s) the certificate is linked to. This way, it’s easy to verify that the SSL certificate for ssl.google-analytics.com is configured correctly, although the SSL certificate for www.google-analytics.com (which gets used whenever e.g. https://www.google-analytics.com/ga.js is requested) doesn’t include *.google-analytics.com as its Common Name or as one of its Subject Alternative Names. Hence the error in IE6.

MrBester wrote on :

Is it just me or does the insertion rely on the presence of a <script> element somewhere in the page? I know it is unlikely that there is a page nowadays that doesn’t have script on it, but:

document.getElementsByTagName('head')[0].appendChild(g);

…guarantees it won’t blow up whereas document.getElementsbyTagName('script') can return []. If there isn’t a zero-th element, there can’t be a .parentNode property…

wrote on :

MrBester: You actually need a <script> element to include the snippet or reference an external .js file containing the snippet, so in those cases document.getElementsbyTagName('script') will never return [].

However, you’re right that there are other (evil) ways to include the snippet — for example, by adding an onload attribute to the body element — but I don’t think anyone will ever complain that Google doesn’t handle these edge cases.

Christof wrote on :

Have you tried to simplify…

ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';

…to…

ga.src = '//www.google-analytics.com/ga.js';

? This way HTTP/HTTPS should be used automatically. I tested this before and seems to work but may be I missed something…

wrote on :

Christof: Erm, that’s exactly what this snippet is doing.

You’re right that this causes HTTP/HTTPS to be used automatically, but the problem is the subdomain is different as well. For HTTP the subdomain is www, for HTTPS it’s ssl.

Christof wrote on :

Ah, did not notice the www/ssl switch, my fault. Point was that you could have lost the additional distinction with (…) ? … : ….

George Gooding wrote on :

Not sure if this has changed recently, but the www-subdomain also works on HTTPS now. Check it: https://www.google-analytics.com/ga.js

So I guess this means that the protocol-relative version will function for both HTTP and HTTPS?

George Gooding wrote on :

Ah OK. IE6 though, usage for that is like less than 3% now in my territory. I might be tempted to let them suffer a security warning...

Simon wrote on :

Thanks Mathias!

I came up with the following adjustment to stop analytics setting cookies on assets that are in a subdomain:

<script>
var _gaq=[['_setAccount','xx-xxxxxxx-xx'],['_setDomainName','www.mysite.com'],['_trackPageview']];
(function(d,t)
{var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.async=1;g.src='http://www.google-analytics.com/ga.js';
s.parentNode.insertBefore(g,s)})(document,'script')</script>

Tracking is working fine, the only thing I can’t seem to fathom is Google site verification via Analytics, thinking the above code could be the problem I placed the stock code back in and still no cigar with verification.

wrote on :

Simon: I can’t seem to get site verification to work using the Analytics option in Google Webmaster Tools either. I strongly doubt this is because of the modified snippet I’m using. Like you, I’ve tested switching back to the default Analytics snippet but that didn’t seem to make a change.

Master James wrote on :

Did you make sure you put the script block in the head or not in the cases above where it’s not verified? I believe the GA site says this specifically, were as before I think we where told to put it at the bottom of the page, I’m guessing that was an asynchronous position? Not sure really. I seem to have the code in both places and I wondered if the ga.js was doing that?

Robbie wrote on :

We had a problem with asynchronous JavaScript trackers, marketing pixels, etc. at Knewton and solved it by building an open-source jQuery plugin called Gatling Analytics that makes any tracker asynchronous. We’ve created definitions for the GA snippets and other common trackers, which are ready to go use, and we’re ready to help define more depending on what people need.

Check it out and see how it compares to the optimizations in this thread. Would love to hear how we can improve Gatling!

Here’s the overview: http://code.knewton.com/post/1293309587/gatling-analytics-make-any-javascript-tracker
It’s on GitHub: https://github.com/knewton/gatling

Robbie

Peter wrote on :

Hi Mathias,

did some further work on your code. Did add the outbound onclick tracking with javascript to add the onclick-attributes on each outgoing link:

for(i=0;i<d.getElementsByTagName('a').length;i++){
a=d.getElementsByTagName('a')[i];
if(!a.getAttribute('onclick'))if(window.location.hostname!=a.hostname)
a.setAttribute('onclick',
"javascript:_gaq.push(['_trackEvent','outbound','"+a.href+"']);");
}

Added line-breaks to make the code readable ;-)

You can generate the code over here.

Best Regards! Peter

wrote on :

Peter: Nice work! However, there are a few things I don’t really like about your script. For example, the i variable will be created in the global scope because you didn’t use the var keyword.

Also, why use two if statements if you could just as well use &&?

Then there’s this:

a.setAttribute('onclick',
"javascript:_gaq.push(['_trackEvent','outbound','"+a.href+"']);");

Why not just use something like the following?

a.onclick = function() {
_gaq.push(['_trackEvent', 'outbound', a.href]);
}

Ruben wrote on :

What's wrong with simply:

<script>var _gaq=[['_setAccount','UA-XXXXX-X'],['_trackPageview']];</script>
<script async src="//www.google-analytics.com/ga.js"></script>

Shorter: check.
Faster: check.
Better: ?

wrote on :

Ruben: The problem is that the async attribute for <script> elements doesn’t work in older browsers. That’s why ga.js is inserted dynamically in the first place. Your code might be shorter, but the second line will block rendering until ga.js is fully loaded in browsers that don’t understand the async attribute. Faster: uncheck :P

Edit: Here’s a link for ya: http://googlecode.blogspot.com/2009/12/google-analytics-launches-asynchronous.html

The second half of the snippet provides the logic that loads the tracking code in parallel with other scripts on the page. It executes an anonymous function that dynamically creates a <script> element and sets the source with the proper protocol. As a result, most browsers will load the tracking code in parallel with other scripts on the page, thus reducing the web page load time. Note here the forward-looking use of the new HTML5 async attribute in this part of the snippet. While it creates the same effect as adding a <script> element to the DOM, it officially tells browsers that this script can be loaded asynchronously.

Ruben wrote on :

Hi Mathias,

Thanks for taking the time to reply!

However, I think some arguments are overlooked:

  1. In what browsers will a script element inserted this way work faster? (“most browsers” = IE x? And note the article dates from 2009, so “most” then is not necessarily “most” now.)
  2. People that actually care about browser speed, have a decent browser. The other ones won’t notice the extra 200ms, IE is terribly slow anyway ;)
  3. We’re not breaking anything in older browsers, it just works as fast as the old synchronous Analytics solution, one would think… Turns out it’s even better: it runs significantly faster, since http://www.google-analytics.com/ga.js is cached over all sites, while the old solution had specific URL parameters. So no blocking there after one initial load. And of course, each page’s code is shorter.

My conclusion is that in 2011 — averaging over an entire browsing session and with the current browser share — my solution would be the shortest and fastest in all browsers.

wrote on :

Ruben:

People that actually care about browser speed, have a decent browser. The other ones won’t notice the extra 200ms, IE is terribly slow anyway ;)

Right, no one is stuck using IE because of corporate IT limitations…

No offense, but with that mindset, you probably shouldn’t be doing WPO.

My conclusion is that in 2011 — averaging over an entire browsing session and with the current browser share — my solution would be the shortest and fastest in all browsers.

Not in all browsers — only in browsers who implement the async attribute correctly, as I explained before.

If you’re averaging over an entire browsing session anyway, you should consider the fact that my JavaScript snippet can be concatenated and minified together with all other scripts used on the site. Of course, that file would only be loaded once (assuming you correctly configured caching on your server).

Your suggestion would still require the code snippet (the two <script> elements) to be included in HTML source of every single page. Compared to my snippet, this will quickly result in a higher aggregated byte size (over a couple of page views).

To summarize, your suggestion is fine if…

  • you don’t care about older browsers lacking support for script/@async;
  • you aren’t using any external JavaScript files where you could append the GA snippet to.

In all other cases, I’d stick with the snippet described above.

John Cooper wrote on :

You recommend placing this script before all other scripts on the page while the HTML5 Boilerplate places this script after all the other script calls are made. I’m curious about the discrepancy. Certainly, it’s not some sort of oversight on their part (Paul Irish et al)…

wrote on :

John: I should point out that it’s Google — not me — recommending to place this script before all other scripts in the document. The only real advantage is to catch a pageView call if your page fails to load completely (for example, if the user aborts loading, or quickly closes the page, etc.). Personally, I wouldn’t count that as a page view, so I actually prefer to place this script at the bottom, after all other scripts. This keeps all the scripts together and reinforces that scripts at the bottom are the right move. (Usually I concatenate and minify all my scripts into one .js file — the GA snippet being the suffix.)

Paul agreed with my stance, so we placed it at the bottom of the HTML5 Boilerplate by default. You can read more about this decision at http://html5boilerplate.com/html5boilerplate-site/built/en_US/docs/html/#google-analytics-tracking-code, under the “Google Analytics Tracking Code” section.

Ruben wrote on :

Mathias:

Right, no one is stuck using IE because of corporate IT limitations…

No offense, but with that mindset, you probably shouldn’t be doing WPO.

None taken, but I just wonder: every WPOer is talking about those poor people stuck with IE. But… where are the numbers and graphs about people using IE against their will (for corporate or other reasons)? You find them on tech websites, but these statistics are skewed. And again, what websites should people browse when they’re working?

Other people just use IE either because they don’t know better or either because they choose to. The first category are people that are not very proficient with computers anyway and will not notice the small change in loading time, because the limiting factor is their own surfing speed. The latter category — I don’t know who they are — has chosen and should carry the consequences of a slow browser.

Not in all browsers [...]

I said “averaging”, so I mean: combine the load times of all your visitors over an entire browsing session and divide them by the total number of page views.

If you’re averaging over an entire browsing session anyway, you should consider the fact that my JavaScript snippet can be concatenated and minified together with all other scripts used on the site.

I think you better use a loader then (the recent load.js is great and tiny), benefiting from CDNs when you’re using Analytics, jQuery etc.

To summarize, your suggestion is fine if…

  • you don’t care about older browsers lacking support for script/@async;

As I argued above, it’s (most of) the users of those older browsers themselves that don’t care, not me. And it’s faster for newer because of less size.

  • you aren’t using any external JavaScript files where you could append the GA snippet to.

Totally true, but that’s a different use case. I would go for the loader then.

My bottom line is: you can measure the number of milliseconds a page loads faster, but that’s not the measure you want to know. The important measure is how it improves the average user experience, and my guess is that it doesn’t affect most people with older browsers that much, since they probably have more important limiting factors anyway.

Joel Mahaffey wrote on :

Hi Mathias,

Thanks for all the work you've put into this article. I was wondering if you could clarify something above -- we've recently restructured our build framework to put most of our JS calls at the end of the body in order to speed up page rendering (and the user experience). You note above that:

Whatever position you choose, just make sure to insert the snippet before all other scripts — if not, the tracking will not begin until the other scripts have been loaded, and you lose the big advantage of asynchronous tracking (i.e. starting the tracking process before the document is finished loading).

Some of our page elements rely on jQuery to load, and as a result, we've put that in the head. Would you recommend moving the Analytics call up to the head as well, since we're already loading some JS in the head, or would you recommend that we leave it at the end of the document, but still put it at the top of the list in the JS being loaded at the end of the body?

Thanks!

wrote on :

Joel: You should always try to place your scripts at the bottom of the document, ideally right before </body>. Google recommends to place the GA snippet in the <head> so it can start tracking sooner, but personally I prefer to place it at the bottom, together with all other scripts. I wrote some more about this in comment #50, if you’re interested.

Biren Shah wrote on :

Mathias,

Our site is using urchin.js and we are thinking of migrating to the new async code. This thread has been pretty valuable so kudos and thanks!!!

Have you come across any studies or measurements to indicate what kind of page load improvements can be achieved typically by moving to the new async method?

Thanks, Biren

Alexander P. wrote on :

Does this work if I want to add the trackPageLoadTime extension onto this code?

var _gaq = [['_setAccount', 'UA-XXXXXXX-X'], ['_trackPageview'], ['_trackPageLoadTime']];

Samuel H. wrote on :

Challenge accepted!

<script>var _gaq=[['_setAccount','UA-XXXXX-X'],['_trackPageview']];
!function(d,t,g){g=d.createElement(t);
d=d.getElementsByTagName(t)[0];g.async=1;g.src='//google-analytics.com/ga.js';
d.parentNode.insertBefore(g,d)}(document,'script')</script>

I killed a few more bytes here though readability got a little worse,

Removed whitespace, parens around the function and prepended a ! bang to hint that it’s immediately invoked, removed www. in the analytics URL.

wrote on :

Samuel: Since most (probably 99.9%) of all sites that use Google Analytics link to http(s)://www.google-analytics.com/ga.js — including the www. visitors are more likely to already have that resource cached. That’s why I decided to keep the www. in :)

Anyhow, to quote the above article:

Of course, it’s possible to reduce the snippet’s byte size even more, for example by using placeholder arguments instead of var, or by getting rid of the immediately-invoked function expression. It’s important to watch out for global namespace pollution though. I’d say the above snippet is the perfect balance between efficiency and readability, and would make a fine replacement for the default snippet provided by Google.

Follow that link to see some examples. These are shorter, but not necessarily better.

Martin wrote on :

Thanks for pointing out the push aggregation. I was not aware that this is possible and it not only reduced my script size (have been using 9 push calls before), but also increased readability.

As far as the HTTP/HTTPS protocol selection is concerned, I have moved the logic from JavaScript into server-side processing, because in my case no site is accessible via both protocols.

wrote on :

BKH: I’m not sure that’s necessarily faster. Most sites that use Google Analytics reference Google’s copy, so it’s very likely users already have the ga.js file cached.

Jeff Byrnes wrote on :

Mathias, I’m curious if you’ve played around with having a +1 button on any sites, and the fact that it seems to insert & load ga.js in the head for its own purposes. I’d love to optimize these things together, and not be loading ga.js twice.

wrote on :

Jeff: Luckily, ga.js won’t actually be downloaded twice if it’s already in the browser cache, so while it’s not ideal to insert the script twice, it doesn’t really hurt much. I don’t know of an easy way around this though.

Boomhauer wrote on :

I’m assuming Google uses the different ssl vs. www prefixes so they can have the option to segment IP pools and let some servers be SSL workhorses while others won’t need to worry about it. Also, if only a handful of requests are SSL, then adding certificates and other infrastructure to every server would be overkill, when a much smaller set of servers can be configured in this way.

Disclaimer: IANAGE (I am not a Google employee ;])

Jeroen wrote on :

Thanks for this code, I love it. I was still using the old code. The only thing I’m walking into is that my bounce percentage increased 6% since implementation, and my time spend on page has decreased 40% too. Is that something that’s normal with the newer code? Have more of you experienced this?

wrote on :

Jeroen: The “new snippet” does exactly the same thing as the old code, only in a more efficient manner. The results you’re seeing have nothing to do with the script you’re using.

Denis Ryabov wrote on :

Remove function call and save 12 bytes (227 bytes in total):

<script>var _gaq=[["_setAccount","UA-XXXXX-X"],["_trackPageview"]],g=document.createElement("script"),s=document.getElementsByTagName("script")[0];g.src="//www.google-analytics.com/ga.js";s.parentNode.insertBefore(g,s)</script>

wrote on :

Denis Ryabov: That would introduce two additional global variables that aren’t needed after the script has been inserted. This is something I made sure to avoid while optimizing the snippet.

Trevin wrote on :

Mathias, I added in the _trackPageLoadTime param since we’re leveraging the new capability from Google. You might want to add this to your original post:

<script>var _gaq=[['_setAccount','UA-19416560-1'],['_trackPageview'],['_trackPageLoadTime']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script'))</script>

JasonDavis wrote on :

Can you please add this to GitHub.com so people can clone it and watch for updates? If not, would you mind if I add it to GitHub with link to your site as source?

Binyamin wrote on :

Mathias, you are not obligated to use ['_trackPageview'], GA gives you the same result without it too.

var _gaq=[['_setAccount','UA-XXXXXXXX-X']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s);}(document,'script'));

wrote on :

Binyamin: Unfortunately, _trackPageview is required to send the data to Google Analytics. If you run your snippet from the console, and keep an eye on the Network tab in your browser’s developer tools, you’ll see that the utm.gif beacon is never requested.

CW wrote on :

Hello Mathias, I’m quite impressed at the optimisation! I would’ve thought the folks at Google would know this type of stuff themselves…

seteh wrote on :

Mathis hello! I just want to say thank you! You optimization was so helpful for me! What are you think about one more optimization - invoking IIFE expression in that way (like facebook guys do):

!function(a,b){}(a,b);

Minus one symbol ;)

wrote on :

seteh: This saves some bytes, but it does cause an extra operation to be performed, i.e. the return value of the function will be negated for no good reason. I personally prefer the regular IIFE approach: (fn());.

wrote on :

Dannii:

Still too much code for my liking.

The solution you suggested relies on jQuery — which is over 30 KB larger than the snippet this blog post is about.

You left out the www. part of the ga.js URL. Since this is not the suggested URL, it’s unlikely a visitor already has this file cached.

Furthermore, you’re using jQuery.getScript, which disables caching (it cache-busts!), causing ga.js to be reloaded for every page. This is a very bad idea in terms of performance. If you insist on using jQuery for this, at least use jQuery.ajax.

You also seem to be using the legacy GA tracking code.

Tobias wrote on :

Hi!

First of all - like already said via Twitter: thank you! This is fantastic stuff!

I put the new code snippet into our single JS file used in production & tracking seems to work just fine. I added ['_anonymizeIp'] for legal reasons.

One thing still bugs me: Google Page Speed kept complaining that I was now loading ga.js synchronously. So I altered the code to say g.src=(g.async='//www.google-analytics.com/ga.js'); to keep Page Speed from nagging. Was that the right way to go about it?

Tobias wrote on :

Mathias: Thanks for your feedback! I thought as much concerning Page Speed, so I did some testing:

The bug is not present in the Page Speed Firebug plugin v. 1.12, but shows in Page Speed Online. I was, however, unable to find out which version Page Speed Online is running on. There’s an identical bug-report back from late 2010 for Page Speed that concerns this exact problem, but I think it is highly unlikely that Google’s running Page Speed Online on such an outdated version. So it might just be a regression.

I’m trying to get in touch with the Page Speed guys & will open a proper bug report if deemed necessary. I removed async from my code again & tested with WebPagetest — all is well in the jungle. :)

wrote on :

Devin Rhode: That way, you might run in to the Operation Aborted error in old IE.

Also, are you sure all browsers automatically create <html> even if it’s omitted from the markup? Since not all of them create <head>, I’d think the same is true for <html> (or whatever the root element is). I don’t know of a case where this fails, but I haven’t tested this thoroughly either, so I’d rather avoid any potential issues and just use the suggested approach.

Andrew wrote on :

Mathias: Probably, this was already mentioned (though I looked through the comments and didn’t find an answer, only a report on the same issue), but you will not get registered on Google’s Webmaster Tools with your site via the snippet if it is not in the <head> of the document. Though placing snippet in the bottom is smart (I’ve read Paul’s and your (Mathias) reasons for that), still it may prevent benefits from using some other Google services (maybe, don’t know yet), just because Google won’t find the snippet where it wants it to be. So, my guess is – consider returning it back to the <head> (though it may count “accidental clicks” as you say) just for the sake of enabling users of boilerplate to use any other services that are (or will be) provided by Google relying on the snippet in the <head>.

Joris van Summeren wrote on :

I did some tests on the Google Webmaster Tools verification. You can still verify a site with the code below:

var _gaq=_gaq||[];_gaq.push(["_setAccount","UA-xxxxx-1"],["_trackPageview"]);

Joris van Summeren wrote on :

Mathias: Yes it should be in the <head>.

I also tested after <head>, at <body> start and end and even after </html> but none of them worked.

Joris van Summeren wrote on :

So this is the code I use in production sites:

<script>var _gaq=_gaq||[];_gaq.push(['_setAccount','UA-xxxxx-1'],['_trackPageview']);(function(b,c){var a=b.createElement(c),d=b.getElementsByTagName(c)[0];a.async=a.src='//www.google-analytics.com/ga.js';d.parentNode.insertBefore(a,d)})(document,'script');</script>

Kevin Suttle wrote on :

Hey Mathias, I made a gist of your latest snippet. https://gist.github.com/4010478 You should do the same and embed it here. Might make life easier on you and readers just now discovering this little gem you've assembled. :)

TangRufus wrote on :

Is it possible to use “the final result” to replace google_analytics.html in the Octopress 2.1 branch?

<script>var _gaq=[['_setAccount','UA-XXXXX-X'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script'))</script>

Thank you.

wrote on :

Binyamin: There’s a difference between someScriptElement.async = true (JavaScript) and using <script async> (HTML). As explained in the post, the spec dictates that dynamically inserted scripts must always be loaded as if there was an async attribute applied to it in the HTML. Thus, there’s no need to set the async attribute value or the async property explicitly through JavaScript in this case. I link to the relevant section in the spec in the article.

wrote on :

Binyamin: The async attribute is useful in HTML, e.g. <script src=foo.js async>. What I’m saying is that as per the HTML spec, there’s no point in adding scriptElement.async = true when inserting a script through JavaScript.

Michael wrote on :

What is your opinion of putting it inside of a seperate file...like <script src=analytics.js> and then include that in the code with the rest of your other seperate js files? I havent done this just curious what you think of doing that.

rctgamer3 wrote on :

Thanks for the useful minified snippet! I've minified it even further by just removing www. from the address, because it works just fine without it :-)

BrilliantMistake wrote on :

In the official (Google) snippet for Universal Analytics, there are a couple of commas where you might expect a semicolon to appear, most notably just after the push(arguments) code, and the date code.

I’m curious as to why this is a comma and not a semicolon, and all I can think is that it’s “chaining” the date stamp code onto the end of a check for the existence of the ga function. That is to say, if the ga function doesn’t exist, create it and datestamp it, but the presence of a semicolon will datestamp regardless. Thoughts on this?

I’m not 100% au fait with commas used this way, and actually, in my own project, I’m using the deobfuscated version! The reason / rationale for this?

Also, although Google recommends the script to be placed inside every page of a site, is it not better to place the snippet within its own .js file, which will then be cached by the browser? Then it becomes a question of: time to download the extra (embedded) circa 300 bytes, vs. the extra circa 30 bytes of a <script src="tracker.js"> plus parsing /loading from cache?

Finally, this may be a crazy question, but why didn’t google place the snippet (or equivalent) in the ga.js code itself, but allow the ID to be passed in the form of ga.js.php?id=xxxx? Assuming you only wish to use one ID… Or would this be considered too much workload on Google’s servers and negate caching?

Binyamin wrote on :

Mathias: Do we really need to use subdomain www.google-analytics.com or would google-analytics.com be preferable?

Under both domains ga.js and analytics.js expires after one day and is not an significant indication where browser might have a long-time cache under www.google-analytics.com (domain that Google offers on default snippet) and we can reduce the snippet size using the no-www domain.

Update: Google Analytics request /collect and /__utm.gif always comes from domain www.google-analytics.com and does not depend on no-www domain in the snippet. So to prefetch one domain <link rel=dns-prefetch href=//www.google-analytics.com> besides of two, the best performance will be delivered on using domain www.google-analytics.com.

David wrote on :

Hi, we are looking to do something using event tracking and not even sure if it’s possible but here goes:

We have a website that distributes content. A user will search for content and then select maybe 20 pieces of content (PDFs, audio files, documents, etc). We then package these files into a streaming zip when the user clicks Download.

This all takes place within a JavaScript App inside Drupal. We are tracking the download click event in Google fine but so far haven’t scoped adding labels. Ideally, we would go deeper than the zip file and pass the names of each file downloaded during the same event. Is there a way we can set up an array to do this or some other means of passing all the file names? As an alternative, can we tell google there were n clicks — one for each file in the download zip?

We may have found the outer edge of Google’s limitations but it would be awesome to find a way to do this.

Cliff wrote on :

I’m having issues with verifying the webmaster via Google Analytics code. Someone told me that the problem could be that I’m using the new Universal code. Is that a possible case?

jorge wrote on :

The website builder tool offered by GoDaddy ( WebsiteTonight) has a space to add JavaScript between <head></head> but for some reason Google Analytics is not working (not only for me but for everybody else)! The only place to add it in that section is using the meta-tag box but the Google Analytics is very long for that space. Is there anyway to reduce the Google Analytics to the half of its size? Please help!

Pestbarn wrote on :

I just tried implementing this in a new HTML5 Boilerplate based project, but I must say I’m a bit clueless on one point, mainly because I’m no javascript ninja. I need to use <a onclick="_gaq.push(['_trackEvent', 'button', 'action', 'id']);">, but with this new snippet, it of course says that _gaq is not defined. How could one do a rewrite for it to work?

Pestbarn wrote on :

Mathias: So basically, switching _gaq.push to l.push in the onclick should work? (using snippet from https://raw.github.com/h5bp/html5-boilerplate/master/index.html)

I’ve tried this, but while it’s not giving me any errors when firing l.push([…]); it’s not fetching __utm.gif?utmwv=… with the correct params as expected either (specifically the utme parameter), and therefore doesn’t trigger the event.

Update: Oh, nevermind. I had completely overlooked the fact that the event tracking code also had changed.

ga('send', 'event', 'buttons', 'action', 'id'); works as a charm, and returns the params ea, ec, and el (category, action, label).

Shane wrote on :

Just wanted to thank you for all this. Not only is it in-depth, but you kept it up to date with recent updates (like the GA universal stuff). I'm writing a third-party javascript snippet and this really helped me structure the code! Thank you so so so much!

Stuart Blackler wrote on :

Thanks for this, I used some of your optimisations whilst I was optimising my own website (also credited a link back to this post). I think that you can go one step further with the optimisations. You can change the place that the script is written to into the head. This helps prevent some of the extra paints when the DOM is re-written. Plus you can remove the www. from the URL. It works without it. If you want to take a look at what I have done, take a look here: http://sblackler.net/2014/01/11/Website-Optimisation-2014/.

Thanks again, Stu.

Régis Kuckaertz wrote on :

Aside from IE8 compat, is there any reason to use document.scripts over document.head? If none, you could write:

scriptElement = document.createElement('script'),
scriptElement.src = '//www.google-analytics.com/analytics.js';
document.head.appendChild(scriptElement);

…and shave a few more bytes off.

Bart Veneman wrote on :

One of the statements below could save you one more byte, but I wonder if it’s worth the loss of readability…

!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();

Source.

BLooperZ wrote on :

check this:

(function(b,l,i,p){b.GoogleAnalyticsObject=i;b[i]||(b[i]=function(){(b[i].q=b[i].q||[]).push(arguments)});b[i].l=+new Date;
l.getElementsByTagName(p)[0].parentNode.appendChild(l.createElement(p)).src='//www.google-analytics.com/analytics.js'}
(window,document,'ga','script'));ga('create','UA-XXXXX-X','auto');ga('send','pageview');

all the script assygnment in one line, still safe as it search for the script element

Federico Brigante wrote on :

Following the realization that <script async> is better than dynamically-added script elements, should we use that? For many of us IE8 is now dead.

<script>
(function(window, document, variableName, scriptElement, firstScript) {
window['GoogleAnalyticsObject'] = variableName;
window[variableName] || (window[variableName] = function() {
(window[variableName].q = window[variableName].q || []).push(arguments);
});
window[variableName].l = +new Date;
}(window, document, 'ga'));

ga('create', 'UA-XXXX-Y');
ga('send', 'pageview');
</script>
<script async src="https://www.google-analytics.com/analytics.js"></script>

and compressed:

<script>!function(window,document,variableName,scriptElement,firstScript){window.GoogleAnalyticsObject=variableName,window[variableName]||(window[variableName]=function(){(window[variableName].q=window[variableName].q||[]).push(arguments)}),window[variableName].l=+new Date}(window,document,"ga"),ga("create","UA-XXXX-Y"),ga("send","pageview");</script><script async src="https://www.google-analytics.com/analytics.js"></script>

12 fewer bytes and earlier download!

I should add that async is not available in IE9, but defer is. For analytics that perfectly fine, too! Just use them both: <script async defer src="…">.

wrote on :

Federico: I agree, and actually this is what I’ve been proposing to do from now on in the HTML5 Boilerplate.

<script>
ga=function(){ga.q.push(arguments)};ga.q=[];ga.l=+new Date;
ga('create','UA-XXXX-Y','auto');ga('send','pageview')
</script>
<script src="https://www.google-analytics.com/analytics.js" async defer></script>

The only downside is that this is not a pure JavaScript solution anymore, meaning it cannot be moved/concatenated into a .js file. OTOH, no one but me seems to be doing that with the GA snippet anyway; everyone just inlines it in the HTML.

Ricardo Henrique wrote on :

I can put this optimized async google analytics in external .js file (this file had another js functions).

Richard Karlos wrote on :

Thanks Mathias:

When i use finalized code with "async defer" it'll stops Analytics to gather data so when i use it without async defer, it starts working good. Why is this error on http://www.urgentfiles.com/ ?

Valtteri wrote on :

It seems that the code can be reduced to following:

html <script>ga={q:[['create','UA-XXXXX-Y'],['send','pageview']],l:+new Date}</script> <script src="https://www.google-analytics.com/analytics.js" async></script>

Leave a comment

Comment on “Optimizing the asynchronous Google Analytics snippet”

Your input will be parsed as Markdown.